﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Timers;
using System.Configuration;

namespace PadiCalClient
{
    [Serializable]
    class Slot
    {
        private int _num;

        private String state;

        private Timer timer = new Timer(Int32.Parse(ConfigurationSettings.AppSettings["commit_timeout"]));

        private bool timerClosed = false;

        private Agenda main;

        private ArrayList reservations = new ArrayList();

        private Reservation assigned = null;

        public Slot(int num, Agenda agenda)
        {
            _num = num;
            this.state = "Free";
            main = agenda;

            timer.Elapsed += new ElapsedEventHandler(timer_Event);

        }

        public void TreatReservation(Reservation res)
        {
            if (res.State.Equals("Initiated"))
            {
                if (this.state.Equals("Free"))
                    this.state = "Acknowledged";

                this.reservations.Add(res.Clone());
            }
            
        }

        public string State
        {
            get { return this.state; }
            set { this.state = value; }
        }

        public ArrayList SlotReservations
        {
            get { return this.reservations; }
            set { this.reservations = value; }
        }

        public ArrayList getAllReservations()
        {
            ArrayList ret = (ArrayList) reservations.Clone();

            if (assigned != null)
            {
                ret.Add(assigned);
            }

            return ret;
        }

        public Reservation getReservation(int id, string host)
        {
            Reservation r;
            for(int i=0; i < this.reservations.Count; i++)
            {
                r = (Reservation) this.reservations[i];
                if(r.getID() == id && r.getHostName().Equals(host))
                    return r;
            }
            return null;
        }

        private bool hasTentativelyReservations()
        {
            Reservation r;
            for (int i = 0; i < this.reservations.Count; i++)
            {
                r = (Reservation)this.reservations[i];
                if (r.State.Equals("Tentatively"))
                    return true;
            }
            return false;
        }

        public bool removeReservation(int id, string host)
        {
            Reservation r;
            for (int i = 0; i < this.reservations.Count; i++)
            {
                r = (Reservation)this.reservations[i];
                if (r.getID() == id && r.Host.Name.Equals(host))
                {
                    if (assigned != null && assigned.Equals(r))
                    {
                        assigned = null;
                        timerClosed = false;
                    }

                    this.reservations.RemoveAt(i);
                    Console.WriteLine("Removed reservation {0} on slot", r.Name);
                    
                    if (!state.Equals("Assigned"))
                    {
                        if (reservations.Count == 0)
                        {
                            state = "Free";
                        }
                        else
                        {
                            if (!hasTentativelyReservations())
                            {
                                state = "Acknowledged";
                            }
                        }
                    }
                    return true;
                }
            }

            return false;

        }

        public bool insertTentativeRes(Reservation res)
        {
 
            if (State.Equals("Assigned"))
            {
                return false;
            }
            else
            {
                if (res.State.Equals("Tentatively"))
                {
                    Reservation r;

                    for (int i = 0; i < this.reservations.Count; i++)
                    {
                        r = (Reservation)this.reservations[i];
                        if (r.getID() == res.getID() && r.getHostName().Equals(res.getHostName()))
                        {
                            this.reservations[i] = res;
                            this.State = "Booked";
                        }
                    }
                }
                else
                {
                    return false;
                }
            }
            return true;
        }

        public void commitReservation(Reservation res)
        {
            State = "Assigned";
            assigned = res;

            Reservation r;
            
            int i = 0;

            while (i < reservations.Count)
            {
                r = (Reservation)reservations[i];
                if (r.getID() != res.getID() || !r.getHostName().Equals(res.getHostName()))
                {
                    main.initiateAbortion((Reservation)r.Clone(), _num.ToString());
                }
                i++;
            }

            reservations.Clear();

            //Remover a reserva nos outros slots
            foreach (string sl in res.Slots)
            {
                main.slots[Int32.Parse(sl)].removeReservation(res.getID(),res.getHostName());
            }
        }

        public void hostCommit(Reservation res)
        {
            getReservation(res.getID(), res.getHostName()).changeToCommit();
        }


        private bool chooseCommitement()
        {
            Reservation choosen = getFirstCommit();
            Console.WriteLine("Time Out! choosen " + choosen.Name);
            if (choosen == null)
            {
                return false;
            }
            else
            {
                if (!choosen.getHostName().Equals(main.user.Name))
                {
                    sendCommitAck(choosen);
                }
                return true;
            }
        }

        private Reservation getFirstCommit()
        {
            Reservation r;
            for (int i = 0; i < reservations.Count; i++)
            {
                r = (Reservation) reservations[i];
                if (r.State.Equals("Commit"))
                {
                    return r;
                }
            }
            return null;
        }

        private void timer_Event(object source, ElapsedEventArgs e)
        {
            if (chooseCommitement())
            {
                timerClosed = true;
            }
            timer.Stop();
        }

        public bool tryCommit(Reservation res)
        {
            if(State.Equals("Assigned") || assigned != null)
            {
                return false;
            }
            else
            {
                if (res.State.Equals("Commit"))
                {
                    getReservation(res.getID(), res.getHostName()).changeToCommit();

                    if (getIndex(res) == 0)
                    {
                        /* Stop timer if started */
                        if (timer.Enabled && !timerClosed)
                        {
                            Console.WriteLine("Timer Stopped!");
                            timer.Stop();
                        }

                        /* Send commitAck to host */
                        sendCommitAck(res);
                    }
                    else
                    {
                        /* begin timeout */
                        if (!timer.Enabled && !timerClosed)
                        {
                            Console.WriteLine("Timer Started!");
                            timer.Start();
                        }
                    }
                }
                else
                {
                    return false;
                }
            }
            return true;
        }

        private void sendCommitAck(Reservation res)
        {
            assigned = res;
            Ack ack = new Ack(main.user.Name, res);
            if (!main.sendAck(ack, res.getHostName()))
            {
                main.abortEntireReservation(res);
            }
        }

        private int getIndex(Reservation res)
        {
            Reservation check;
            for (int i = 0; i < reservations.Count; i++)
            {
                check = ((Reservation)reservations[i]);
                if (check.getID() == res.getID() && check.getHostName().Equals(res.getHostName()))
                {
                    return i;
                }
            }
            return -1;
        }

    }
}
